package com.boe.csb.core.common.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.concurrent.Callable;

/**
 * Created by tianxiang.luo on 16/12/27.
 */
public class RetryUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(RetryUtil.class);
    private static final long MAX_SLEEP_MILLISECOND = 60000;


    /**
     * 重试不成功，则直接忽略
     *
     * @param callable
     * @param retryTimes
     * @param sleepTimeInMilliSecond
     * @param exponential
     * @param <T>
     * @return
     */
    public static <T> T retryOrDiscard(Callable<T> callable, int retryTimes,
                                       long sleepTimeInMilliSecond, boolean exponential, String message) {
        try {
            return retryOrThrow(callable, retryTimes, sleepTimeInMilliSecond, exponential, message);
        } catch (Exception e) {
            LOGGER.warn(String.format("%s retryOrDiscard has Exception(%s),but will be discard",message, e.getMessage()), e);
            return null;
        }
    }

    /**
     * 重试不成功，则抛出RuntimeException
     *
     * @param callable
     * @param retryTimes
     * @param sleepTimeInMilliSecond
     * @param exponential
     * @param <T>
     * @return
     */
    public static <T> T retryOrThrowRuntimeException(Callable<T> callable, int retryTimes,
                                                     long sleepTimeInMilliSecond, boolean exponential, String message) {
        try {
            return retryOrThrow(callable, retryTimes, sleepTimeInMilliSecond, exponential, message);
        } catch (Exception e) {
            LOGGER.warn(String.format("%s retryOrThrowRuntimeException has Exception(%s),will throw runtimeException",message, e.getMessage()), e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 重试不成功，则抛出对应异常
     *
     * @param callable
     * @param retryTimes
     * @param sleepTimeInMilliSecond
     * @param exponential
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> T retryOrThrow(Callable<T> callable, int retryTimes,
                                     long sleepTimeInMilliSecond, boolean exponential, String message) throws Exception {
        if (null == callable) {
            throw new IllegalArgumentException("系统编程错误, 入参callable不能为空 ! ");
        }

        if (retryTimes < 1) {
            throw new IllegalArgumentException(String.format(
                    "系统编程错误, 入参retrytime[%d]不能小于1 !", retryTimes));
        }

        Exception saveException = null;
        for (int i = 0; i < retryTimes + 1; i++) {
            try {
                return callable.call();
            } catch (Exception e) {
                LOGGER.warn(String.format("RetryUtil# [%s] current retry Times [%s], last fail message => [%s]", message, i + 1, e.getMessage()));
                saveException = e;
                if (i < retryTimes) {
                    if (sleepTimeInMilliSecond <= 0) {
                        continue;
                    } else {
                        long timeToSleep;
                        if (exponential) {
                            timeToSleep = sleepTimeInMilliSecond
                                    * (long) Math.pow(2, i);
                        } else {
                            timeToSleep = sleepTimeInMilliSecond;
                        }

                        if (timeToSleep >= MAX_SLEEP_MILLISECOND) {
                            timeToSleep = MAX_SLEEP_MILLISECOND;
                        }

                        try {
                            Thread.sleep(timeToSleep);
                        } catch (InterruptedException unused) {
                        }
                    }
                }
            }
        }

        if (saveException == null) {
            //不应该走到这里来
            throw new RuntimeException("重试方法多次失败，终止重试 .");
        }

        throw saveException;
    }

    /**
     * 重试次数工具方法.
     *
     * @param callable               实际逻辑
     * @param retryTimes             最大重试次数（>1）
     * @param sleepTimeInMilliSecond 运行失败后休眠对应时间再重试
     * @param exponential            休眠时间是否指数递增
     * @param <T>                    返回值类型
     */
    public static <T> T executeWithRetry(Callable<T> callable, int retryTimes,
                                         long sleepTimeInMilliSecond, boolean exponential) throws Exception {
        if (null == callable) {
            throw new IllegalArgumentException("系统编程错误, 入参callable不能为空 ! ");
        }

        if (retryTimes < 1) {
            throw new IllegalArgumentException(String.format(
                    "系统编程错误, 入参retrytime[%d]不能小于1 !", retryTimes));
        }

        Exception saveException = null;
        for (int i = 0; i < retryTimes + 1; i++) {
            try {
                return callable.call();
            } catch (IOException e) {
                saveException = e;
                if (i < retryTimes) {
                    if (sleepTimeInMilliSecond <= 0) {
                        continue;
                    } else {
                        long timeToSleep = 0;
                        if (exponential) {
                            timeToSleep = sleepTimeInMilliSecond
                                    * (long) Math.pow(2, i);
                        } else {
                            timeToSleep = sleepTimeInMilliSecond;
                        }
                        try {
                            Thread.sleep(timeToSleep);
                        } catch (InterruptedException unused) {
                        }
                    }
                }
            }
        }

        if (saveException == null) {
            // 理论上是不会到这里的
            throw new RuntimeException("重试方法多次失败，终止重试 .");
        }

        throw saveException;
    }
}
